Sí aparece en el archivo 01FTR.pdf. Para cada equipo aparece ‘Number of Attacks:’
Se observa sin embargo, que estos números están incorrectos pues la máxima diferencia de posesiones en un partido es de 1.
Ya que no puede haber diferencia en posesiones de ambos equipos en más de una, el dato en el archivo FRT es incorrecto.
En el listado de “jugada a jugada” sí se puede calcular el número de posesiones.
Se descargan todos los archivos pdf de la página del partido con la función scrape_from_ihf creada en R. Se pueden ver los detalles de esta función acá.
en <- 'https://www.ihf.info/competitions/men/308/27th-ihf-men039s-world-championship-2021/22415/match-center/22525'
egipto21::scrape_from_ihf(
enlace = en,
carpeta = 'scraping')Para contar el número de posesiones, he generado una función generar_pbp_tidy de la que se pueden ver detalles acá: que toma el archivo en pdf que hemos descargado y convierte la tabla de jugadas a un formato ‘tidy’. De esta forma, podremos trabajar nuestros datos de manera más sencilla.
Al revisar más a fondo este partido, se observa un error importante (hay otros menores) en el pdf, donde hizo falta una posesión chilena, en este caso la posesión número 101. Esta fue corregida manualmente, actualizando así el archivo .csv de este partido. El error fue encontrado, pues entre un gol de Egipto, sigue inmediatamente un tiro de Egipto, sin que haya respuesta (por pérdida, tiro, o falta técnica) de Chile.
Veamos la cantidad de posesiones totales y la cantidad de posesiones por equipo
library(magrittr)
options(digits = 3)
data <- data.table::fread('partido1_corregido.csv')
message('Cantidad total de posesiones: ', data[, data.table::uniqueN(numero_posesion)])## Cantidad total de posesiones: 112
tabla <- data[, .(`Cantidad de posesiones` = data.table::uniqueN(numero_posesion)),
by = .(Equipo = posesion)]| Equipo | Cantidad de posesiones |
|---|---|
| EGY | 56 |
| CHI | 56 |
Se analizan más de cerca aquellas posesiones donde hay más de un tiro. Estas posesiones fueron contrastadas con el video para observar que estuvieran correctamente marcadas.
posesiones_multi_lanzamiento <-
data[, .(cantidad_de_lanzamientos = sum(!is.na(gol_numero), !is.na(tiro_numero)))
,by = numero_posesion
][cantidad_de_lanzamientos > 1]$numero_posesion
mas_de_un_tiro <- data[numero_posesion %in% posesiones_multi_lanzamiento,
.(tiempo, accion, posesion, numero_posesion)] | tiempo | accion | posesion | numero_posesion |
|---|---|---|---|
| 9:21 | SALINAS E Shot centre 6m post | CHI | 18 |
| 9:26 | SALINAS E Goal centre 6m bottom left | CHI | 18 |
| 9:35 | ELWAKIL O 7m received | EGY | 19 |
| 9:35 | SANAD M Penalty shot saved middle left | EGY | 19 |
| 9:35 | AYALA D 7m caused | EGY | 19 |
| 9:35 | AYALA D 2-minutes suspension | EGY | 19 |
| 10:17 | ELWAKIL O Goal left wing top right (39 ELDERAA Y) | EGY | 19 |
| 10:36 | SALINAS Efor 1 BARRIENTOS F Empty goal | EGY | 19 |
| 40:32:00 | FEUCHTMANN H Shot left wing saved bottom centre | CHI | 73 |
| 40:34:00 | OMAR Y 2-minutes suspension | CHI | 73 |
| 40:36:00 | Team timeout | CHI | 73 |
| 40:46:00 | FEUCHTMANN E Goal breakthrough bottom centre | CHI | 73 |
| 40:55:00 | MAMDOUH Mfor 96 ELTAYAR M Empty goal | CHI | 73 |
| 42:38:00 | AYALA D Shot right wing saved middle right | CHI | 77 |
| 43:30:00 | AYALA D Shot right wing saved top centre | CHI | 77 |
| 44:44:00 | MOAMEN A Shot centre 6m saved bottom left | EGY | 80 |
| 45:11:00 | KADDAH H Shot centre 9m saved bottom right | EGY | 80 |
En las posesiones 77 y 80, se hizo un tiro adicional, pero no se anotó.
se verifican las posesiones donde se perdió balón o se hizo falta técnica, para observar si en esa misma posesión también se hizo algun tiro antes.
posesiones_con_perdida_y_tiro <-
data[numero_posesion %in%
data[stringr::str_detect(accion, 'Technical') |
!is.na(turnover)]$numero_posesion & posicion_marco != '',
.(tiempo, accion, posesion, numero_posesion)]| tiempo | accion | posesion | numero_posesion |
|---|---|---|---|
| 36:03:00 | SALINAS R Shot right 9m saved middle left | CHI | 65 |
| 38:12:00 | OMAR Y Shot breakthrough saved bottom left | EGY | 68 |
Se observa que cada equipo hizo un lanzamiento y luego perdió el balón sin lograr anotar.
En este caso se pueden ver estadísticas generales del partido, en formato xlm.
Además, existen 4 archivos en pdf:
FTR: Tiene las estadísticas de cada jugador, su tiempo de juego, posiciones de lanzamientos y salvadas del portero. Además tiene un resumen de los lanzamientos por equipo.
MTR: Contiene información de lanzamientos y la línea de tiempo de las anotaciones.
OMR: Es la planilla llena al finalizar el del partido.
PBP: Información jugada por jugada.
Observemos algunas estadísticas del partido:
posesiones_con_lanzamiento <-
data[, posesion_con_lanzamiento := any(posicion_marco != ''), numero_posesion
][,.N, .(posesion_con_lanzamiento, numero_posesion, posesion)
][, .(`Posesiones con lanzamiento` = sum(posesion_con_lanzamiento)),
.(Equipo = posesion)]
resumen <- data[, .(Posesiones = data.table::uniqueN(numero_posesion),
Goles = sum(gol, na.rm = TRUE),
`Pérdidas de balón` = sum(!is.na(turnover)),
`Faltas técnicas` = sum(stringr::str_detect(accion, 'Technical'))),
.(Equipo = posesion)] %>%
data.table::merge.data.table(posesiones_con_lanzamiento, all.x = TRUE,
by = 'Equipo')
efectividad <- data.table::copy(resumen)
efectividad <-
efectividad[, `Efectividad Tiros por Posesion` :=
scales::percent(`Posesiones con lanzamiento`/`Posesiones`)
][, `Efectividad Goles por Posesion` := scales::percent(`Goles`/`Posesiones`)
][, `Efectividad Lanzamientos` := scales::percent(Goles/`Posesiones con lanzamiento`)
][,.(Equipo, `Efectividad Tiros por Posesion`,
`Efectividad Goles por Posesion`, `Efectividad Lanzamientos`)] %>%
data.table::transpose(make.names = 'Equipo', keep.names = 'Estadística')
resumen <- data.table::transpose(resumen, make.names = 'Equipo',
keep.names = 'Estadística')
resumen[, diferencia := CHI - EGY]| Estadística | CHI | EGY | diferencia |
|---|---|---|---|
| Posesiones | 56 | 56 | 0 |
| Goles | 29 | 35 | -6 |
| Pérdidas de balón | 12 | 5 | 7 |
| Faltas técnicas | 6 | 6 | 0 |
| Posesiones con lanzamiento | 39 | 46 | -7 |
| Estadística | CHI | EGY |
|---|---|---|
| Efectividad Tiros por Posesion | 70% | 82% |
| Efectividad Goles por Posesion | 52% | 62% |
| Efectividad Lanzamientos | 74.4% | 76.1% |
Se puede ver que Chile hizo lanzamientos en 7 posesiones menos que Egipto. Podríamos pensar que se debe a la diferencia de pérdidas de balón únicamente, pero analicemos esto más a detalle.
Es decir, de haber dejado de perder el balón (o hecho falta técnica) 7 veces durante el partido y convertido eso en lanzamientos, Chile pudo haber estado más cerca del marcador.
Si bien las pérdidas de balón tuvieron gran importancia, es apresurado concluir que la diferencia de 6 goles se debe únicamente a eso. Supongamos una probabilidad de conversión de lanzamiento uniforme durante el partido, es decir, para Chile un 74.4%: de esta forma en 6 tiros adicionales esperaríamos 4.5 goles adicionales.
\(~\)
Se calculan ahora los goles esperados, lo que dará una idea de la calidad de lanzamientos que tuvo cada equipo. Para esto, una posibilidad es tomar un marco de referencia externo al mundial, con muchos más tiros. Por ejemplo un análisis de 75,000 tiros de la Bundesliga. Estos goles esperados están mucho más detallados y segregados que aquellos que podemos tomar de los play by play de este mundial. En este enlace se puede ver la tabla con goles esperados por tipo de tiro.
Para este ejercicio, se toman todos los partidos de la fase de grupos como referencia. Si bien no es una gran cantidad de datos, es suficiente para el análisis y la creación de este modelo discreto (que con más datos, por ejemplo espaciales del punto exacto del lanzamiento o velocidad del jugador, se podría trabajar con un modelo más continuo, comparando con los que encontramos actualmente para balonmano).
Se cargan todos los partidos de la fase de grupos para analizar los tiros. Este archivo fue generado con todos los play by play de la fase de grupos gracias a la función generar_pbp_tidy mencionada anteriormente. Acá simplemente descargamos ese archivo y tomamos a los equipos que nos interesan.
partidos <-
data.table::fread(
'https://raw.githubusercontent.com/telaroz/egipto21/main/partidos_fase1.csv')
equipos_analisis <- partidos[id_partido %in% c(1, 17, 18, 33, 34)]
contrincantes <- split(equipos_analisis, equipos_analisis$id_partido) %>%
purrr::map(~unique(.$equipo))
dt <- contrincantes %>% t() %>% data.table::as.data.table()
equipos_analisis <- split(equipos_analisis, equipos_analisis$id_partido) %>%
purrr::map2(contrincantes, ~ .x[,contrincante := setdiff(.y, equipo), 1:nrow(.x)]) %>%
data.table::rbindlist()Ahora se genera la tabla de goles esperados por posición de tiro, que simplemente es la división entre la cantidad de goles anotados entre el total de tiros hecho por cada posición.
partidos <- partidos[,.(gol, posicion_tiro)]
partidos[is.na(gol), gol := 0]
tiros <- partidos[posicion_tiro != '']
esperados <- tiros[, .(xg = sum(gol)/.N, cantidad_tiros = .N), posicion_tiro][order(-xg)]| posicion_tiro | xg | cantidad_tiros |
|---|---|---|
| fast break | 0.767 | 459 |
| Penalty | 0.763 | 338 |
| empty goal | 0.759 | 87 |
| breakthrough | 0.758 | 524 |
| left 6m | 0.722 | 176 |
| right 6m | 0.722 | 158 |
| centre 6m | 0.705 | 525 |
| right wing | 0.641 | 298 |
| left wing | 0.640 | 289 |
| right 9m | 0.412 | 284 |
| left 9m | 0.384 | 250 |
| centre 9m | 0.381 | 756 |
Ahora, para cada tiro, se agrega su xg.
En este punto no basta sumar los goles esperados por tiro para determinar los goles esperados totales, pues esta métrica se debe hacer por posesión. De lo contrario, podríamos estar contando más de un gol por posesión. Por esto, en posesiones con múltiples tiros, calculamos los goles esperados de esa posesión como el opuesto de la probabilidad de fallar todos los tiros. Se puede resumir en una formula así: \(xG_{posesion} = 1 - \prod_1^n(1-(xG_{tiro})_n)\)
pos <- data[!is.na(xg),
.(xg, posesion, numero_posesion, posicion_tiro, gol, equipo)]
pos[, cantidad_tiros := .N, numero_posesion]
pos[, xg_ajustado := 1 - prod(1-xg), numero_posesion]
pos[, gol_en_posesion := any(gol), numero_posesion]
xg_final <- pos[, head(.SD, 1), numero_posesion]
xg_final <- xg_final[, .(`Goles Esperados` = sum(xg_ajustado)), equipo
][, Goles := data[,sum(gol), equipo]$V1
][, Diferencia := Goles - `Goles Esperados`]| equipo | Goles Esperados | Goles | Diferencia |
|---|---|---|---|
| EGY | 29.9 | 35 | 5.15 |
| CHI | 26.6 | 29 | 2.43 |
Se observa que bajo el marco de referencia de un equipo de fase de grupos del mundial, Egipto anotó 5 goles más de lo esperado y Chile poco más de 2 goles adicionales. Comparando 5.15 y 2.43, se podría señalar que Egipto fue 2 a 3 goles más efectivo en sus tiros que Chile.
Ahora se analizan los goles esperados y anotados de los 3 partidos de cada equipo en la fase de grupos:
equipos_analisis[esperados, xg := i.xg, on = 'posicion_tiro']
equipos_analisis[is.na(gol), gol := 0]
goles_eq <- equipos_analisis[, .(Goles = sum(gol)), .(equipo, id_partido)]
pos_eq <- equipos_analisis[!is.na(xg),
.(id_partido, xg, posesion, numero_posesion,
posicion_tiro, gol, equipo)]
pos_eq[, cantidad_tiros := .N, .(numero_posesion, id_partido)]
pos_eq[, xg_ajustado := 1 - prod(1-xg), .(numero_posesion, id_partido)]
pos_eq[, gol_en_posesion := any(gol), .(numero_posesion, id_partido)]
xg_final_eq <- pos_eq[, head(.SD, 1), .(numero_posesion, id_partido)]
xg_final_eq <- xg_final_eq[, .(`Goles Esperados` = sum(xg_ajustado)), .(equipo, id_partido)]
xg_final_eq[goles_eq, Goles := i.Goles, on = c('equipo', 'id_partido')
][, Diferencia := Goles - `Goles Esperados`]
xg_final_eq[equipos_analisis, Contrincante := i.contrincante, on = c('id_partido', 'equipo')]
xg_final_eq <- xg_final_eq[equipo %in% c('EGY', 'CHI')]library(ggplot2)
plot <- ggplot(data = xg_final_eq) +
geom_point(aes(x = `Goles Esperados`, y = Goles,
color = equipo, label = Contrincante), size = 3) +
geom_abline() +
theme_minimal() +
xlim(0, 40) +
ylim(0, 40) +
scale_colour_manual(values = c('#cc1f12', '#0f0b0b'))
plotly::ggplotly(plot)En este gráfico se observa que en los 3 partidos, Egipto está por encima de la línea de goles esperados. Sin embargo, Chile solamente está en esta situación justamente el partido con Egipto. Aunque la muestra es pequeña, se puede pensar que Chile fue particularmente efectivo en este partido contra Egipto. (Este gráfico es interactivo si se lee este archivo en formato html, por lo que se puede observar en cada punto de qué partido se trata y su información de goles anotados y esperados).
Se analiza ahora la efectividad de Chile por posición (en general tiros, no posesiones):
data <- data[,.(gol, posicion_tiro)]
data[is.na(gol), gol := 0]
tiros <- data[posicion_tiro != '']
esperados_chile <- tiros[, .(xg = sum(gol)/.N, cantidad_tiros = .N), posicion_tiro
][order(-xg)]| posicion_tiro | xg | cantidad_tiros |
|---|---|---|
| right 6m | 1.000 | 4 |
| empty goal | 1.000 | 4 |
| breakthrough | 0.929 | 14 |
| centre 6m | 0.833 | 12 |
| fast break | 0.833 | 12 |
| left 6m | 0.750 | 4 |
| right wing | 0.667 | 9 |
| Penalty | 0.600 | 5 |
| left wing | 0.571 | 7 |
| right 9m | 0.500 | 6 |
| centre 9m | 0.333 | 12 |
| left 9m | 0.000 | 1 |
Sin duda un partido no es una muestra suficiente, pero en este partido los lanzamientos por el centro de 9m fueron altamente utilizados y poco efectivos. Sin embargo, algunos tiros de estos fueron condicionados por la amenaza del pasivo.
Se puede analizar también la dependecia de ciertos jugadores en cada equipo. Para esto del archivo ya descargado ‘01FRT.PDF’ se puede obtener la distribución de minutos por jugador durante este partido. Se cargan los datos, se extraen las tablas y se limpia el tiempo para obtener minutos enteros jugados.
library(magrittr)
tables <- tabulizer::extract_tables('scraping/01FTR.PDF', method = 'stream')
equipos <- purrr::keep(tables, ~ ncol(.x) == 17) %>%
purrr::map(data.table::as.data.table) %>%
purrr::map(~.x[stringr::str_detect(V1, '\\d+'),
.(numero = V1, jugador = V2, Tiempo_jugado = V17)])
equipos[[1]][, Equipo := 'EGY']
equipos[[2]][, Equipo := 'CHI']
equipos <- data.table::rbindlist(equipos)
equipos[, `Tiempo jugado` := round(as.numeric(lubridate::ms(Tiempo_jugado))/60)]
equipos[, Jugador := paste(numero, '', jugador)]
equipos_limpio <- equipos[!is.na(`Tiempo jugado`)]Ahora, se visualiza cómo se distribuyen los minutos por jugador (reconocimiento completo a la idea de este gráfico a Edu Agulló).
library(ggplot2)
library(ggbeeswarm)
plot <- ggplot(equipos_limpio, aes(x = Equipo,
y = `Tiempo jugado`,
col = Equipo,
label = Jugador)) +
geom_violin(adjust = 0.75) +
geom_beeswarm(size = 1) +
theme_minimal() +
scale_colour_manual(values = c('#cc1f12', '#0f0b0b'))
ggply <- plotly::ggplotly(plot, tooltip = c('x', 'label', 'y'))
ggply$x$data[[1]]$hoverinfo <- "none"
ggply$x$data[[2]]$hoverinfo <- "none"
ggplySe observa que 5 jugadores chilenos tuvieron una carga mayor a los 40 minutos contra únicamente 3 jugadores de Egipto. Egipto distribuye más hacia el centro los minutos de cada uno de sus jugadores: pocos jugadores jugaron muy poco y pocos jugadores jugaron mucho.
Como conclusión se puede decir que si bien las pérdidas de balón chilenas tuvieron una influencia muy importante en el marcador final, Egipto fue más efectivo en las ocasiones que tuvo, administrando mejor sus jugadores durante el partido.